#Base class file
#Authors: Greg Dorman, Lewis Gordon
import random
random.seed()

class Class:
	#Base stats
	def __init__(self):
		self.Stam = round(random.randint(100,125))
		self.HP = (self.Stam * 10)
		self.MaxHP = self.HP
		self.Str = random.randint(30,40)
		self.Int = random.randint(50,70)
		self.Agi = random.randint(30,40)
		self.Def = random.randint(20,25)		
		self.Mana = (self.Int * 50)
		self.MaxMana = (self.Int * 50)
		self.Scrit = round((((self.Int * 4) * 0.2) / 3), 2)
		self.Mcrit = round(((self.Agi * 3) / 7), 2)
		self.Blk = round(((self.Str + self.Def) * 0.3), 2)
		self.Avd = round((self.Agi * 0.273) , 2)
		self.Sresist = random.randint(10,20)
		self.Stun = 0
		self.Abilities = []
		self.BuffList = []
		self.DebuffList = []
		self.initAbilities()
	
	def updateHealthAndOutputChanges(self, Dmg, otherPlayer):
		otherPlayer.HP -= (Dmg - otherPlayer.Def)
		# If the player's health drops below zero keep it capped at zero.
		if(otherPlayer.HP < 0):
			otherPlayer.HP = 0
		print(self.Name, " attacks "+otherPlayer.Name+" for ", round(Dmg), " damage!")
		print(otherPlayer.Name, " has ", round(otherPlayer.HP), " hit points remaining.\n")
	
	def initAbilities(self):
		raise NotImplementedError()
	
	def printAbilities(self):
		print("Choose your ability:")
		i=0
		for ability in self.Abilities:
			print("("+repr(i+1)+"): "+ ability.Name.title())
			i+=1
	
	def choosePlayersToAttack(self, players):
		#Initialize and populate the target player list.
		#Obviously the attacking player is omitted from
		#the list.
		target_player_list = []
		i=1
		for target_player in players:
			if not target_player.isAlive():
				continue
			if(self==target_player):
				continue
			target_player_list.append(target_player)
			
		#If there is only one player in the target player
		#list then choose that player.
		if len(target_player_list) is 1:
			return target_player_list[0]
		while 1:
			#Print out all players in the target list along with
			#their hp.
			for target_player in target_player_list:
				print("(",i,") ",target_player.Name," has ",round(target_player.HP)," HP left.")
				i+=1
			
			player_num = input("Choose a player to attack:")
			try:
				allSelections = player_num.split(",")
				selectedPlayerList = []
				for selection in allSelections:
					selectedPlayerList.append(target_player_list[int(selection)-1])
				return selectedPlayerList
			#Protecting against garbage input.
			except ValueError:
				print("Invalid value to input.")
			#Make sure that the input is in the array bounds.
			except IndexError:
				print("Selection is not within list bounds.\n")
			finally:
				i=1
	
	def choosePlayerToAttack(self, players):
		#Initialize and populate the target player list.
		#Obviously the attacking player is omitted from
		#the list.
		target_player_list = []
		i=1
		for target_player in players:
			if not target_player.isAlive():
				continue
			if(self==target_player):
				continue
			target_player_list.append(target_player)
			
		#If there is only one player in the target player
		#list then choose that player.
		if len(target_player_list) is 1:
			return target_player_list[0]
		while 1:
			#Print out all players in the target list along with
			#their hp.
			for target_player in target_player_list:
				print("(",i,") ",target_player.Name," has ",round(target_player.HP)," HP left.")
				i+=1
			
			player_num = input("Choose a player to attack:")
			try:
				if player_num == "0":
					raise IndexError
				return target_player_list[int(player_num)-1]
			#Protecting against garbage input.
			except ValueError:
				print("Invalid value to input.")
			#Make sure that the input is in the array bounds.
			except IndexError:
				print("Selection is not within list bounds.\n")
			finally:
				i=1
	
	def getSpellInput(self):
		self.printAbilities()
		spellNum = input('Selection: ')
		try:
			if spellNum=="0":
				raise IndexError
			return self.Abilities[int(spellNum)-1]
		except:
			print("Invalid input")
			return self.getSpellInput()
	
	#Prompts the user for a spell name.  If the spell is on cooldown it returns
	#-1 to start the process over again.  It then checks if the spell is an AoE spell
	#If it is an AoE spell the user is prompted to choose a certain amount of players.
	#If the spell isn't an AoE spell the user is prompted to choose a player.  The
	#ability is then used and the user is prompted about the changes to each damaged player's
	#health.
	def useSpell(self,players):
		ability = self.getSpellInput()
		if(ability.isOnCooldown()):
			print("Ability is on cooldown!")
			return -1
		otherPlayers = []
		if(ability.isAOE()==False or len(players)==2):
			otherPlayers.append(self.choosePlayerToAttack(players))
		else:
			otherPlayers = self.choosePlayersToAttack(players)
		for otherPlayer in otherPlayers:
			Dmg = ability.use(otherPlayer)
			self.updateHealthAndOutputChanges(Dmg,otherPlayer)
	
	#Returns true if the player is alive, otherwise
	#returns false.
	def isAlive(self):
		if(self.HP>0):
			return True
		return False
	
	#See addDebuff	
	def addBuff(self,newBuff):
		for buff in self.BuffList:
			#if spell is already in the list, update the time
			if(buff == newBuff):
				buff.timeRemaining = newBuff.timeRemaining
				return
		self.BuffList.append(newBuff)
		newBuff.statChange(self)
	
	#See updateDebuffs	
	def updateBuffs(self):
		for buff in self.BuffList:
			#if the spell's time remaining is 0 remove and debuff
			if(buff.timeRemaining == 0):
				buff.removeStatChange(self)
				self.DebuffList.remove(buff)
			else:
				buff.effect(self)
				buff.decrementTime()
	
	#Adds a debuff to the list of debuffs.  If the debuff is already
	#in the list, then the effect and the time are updated.
	def addDebuff(self,newDebuff):
		for debuff in self.DebuffList:
			#if spell is already in the list, update the time
			if(debuff == newDebuff):
				debuff.timeRemaining = newDebuff.timeRemaining
				return
		self.DebuffList.append(newDebuff)
		newDebuff.statChange(self)
		
	#Reduces the remaining uptime for all the debuffs.  If a
	#debuff is uptime is zero it removes the debuff from the
	#debuff list and removes the effect.  Also performs the
	#DoT tick for all the DoTs applied to the player.
	def updateDebuffs(self):
		for debuff in self.DebuffList:
			#if the spell's time remaining is 0 remove and debuff
			if(debuff.timeRemaining == 0):
				debuff.removeStatChange(self)
				self.DebuffList.remove(debuff)
			else:
				debuff.effect(self)
				debuff.decrementTime()
		
	#Goes through all the cooldowns and subtracts 1 turn
	def updateCooldowns(self):
		for ability in self.Abilities:
			ability.decrementCD()
		

	#Returns true if the player is stunned.  False if the player is
	#not stunned	
	def isStunned(self):
		if self.Stun == 0:
			return False
		return True
		
	#Sets how long the player is stunned for.
	def stun(self,num):
		self.Stun = num
		
	#Reduces the time on the stun timer by 1 turn.
	def updateStunTimer(self):
		if(self.Stun>0):
			self.Stun-=1
	
	#Sets the player's HP to 0.
	def kill(self):
		self.HP=0
	
	#Allows the user to choose a player from the list of players and 
	#perform a melee attack
	def meleeAttack(self, players):
		targetPlayer = self.choosePlayerToAttack(players)
		
		Dmg = random.randint(5,7) + (self.Str * random.uniform(1,1.4))
		if (targetPlayer.Avd >= random.randint(1,100) and targetPlayer.isStunned() == False):
			print(targetPlayer.Name," avoids the attack!\n")
		else:
			if(self.Mcrit >= random.uniform(1,100)): #If they player lands a critical hit for 200% damage
				Dmg *= 2
				print(self.Name, " lands a critical hit for double damage!")
				
			if(targetPlayer.Blk >= random.uniform(1,100) and targetPlayer.isStunned() == False): #If they player blocks, the damage is reduced by 40%
				Dmg = (Dmg * 0.6) #Damage is reduced by 40% if blocked
				print(targetPlayer.Name, " blocks the attack, reducing the damage taken!")
			if(Dmg < 0):
				Dmg = 0
			self.updateHealthAndOutputChanges(Dmg,targetPlayer)
			
		